// file:system/task/process.c
// autor:jiangxinpeng
// time:2021.6.23
// copyright:(C) 2020-2050 by jiangxinpeng,All right are reserved.

#include <os/memcache.h>
#include <os/task.h>
#include <os/exec.h>
#include <os/process.h>
#include <os/debug.h>
#include <os/kernelif.h>
#include <os/fs.h>
#include <os/pthread.h>
#include <os/memspace.h>
#include <os/fd.h>
#include <os/elf32.h>
#include <os/safety.h>
#include <os/schedule.h>
#include <arch/task.h>
#include <lib/unistd.h>
#include <lib/assert.h>
#include <lib/errno.h>

static int ProcessLoadSegment(int fd, uint64_t off, uint64_t filez, uint64_t memsz, uint64_t vaddr)
{
    uint64_t vpage = vaddr & PAGEALIGN_MASK;
    uint64_t first_page_size = PAGE_SIZE - (vaddr & PAGE_LIMIT);
    uint64_t left_size = 0;
    uint64_t pages = 0;
    uint64_t pyaddr = 0;

    if (memsz > first_page_size)
    {
        left_size = memsz - first_page_size;
        pages = DIV_ROUND_UP(left_size, PAGE_SIZE) + 1;
    }
    else
        pages = 1;

    pyaddr = MemSpaceMap(cur_task->vmm, vpage, 0, pages * PAGE_SIZE, PROTE_USER | PROTE_WRITE | PROTE_EXEC, MEM_SPACE_MAP_FIXED);
    if (pyaddr <= 0)
    {
        KPrint("%s: mem space map vbase:%x pages=%d failed!\n", __func__, vpage, pages);
        return -1;
    }
    KFileLSeek(fd, off, SEEK_SET);
    if (KFileRead(fd, (void *)PTYPE(vpage), filez) != filez)
    {
        KPrint(PRINT_ERR "%s: load segment from file failed! fd=%d,off=%d,size=%d\n", __func__, fd, off, filez);
        return -1;
    }
    KPrint("[process] read segment to mem ok!\n");
    return 0;
}

int ProcessLoadImage(vmm_t *vmm, elf32_header_t *header, int fd)
{
    elf32_segment_t seg_header;
    Elf32_Off seg_header_off = header->e_phoff;
    Elf32_Half seg_header_size = header->e_phensize;
    uint64_t idx = 0;
    uint64_t seg_end;

    while (idx++ < header->e_phnum)
    {
        memset(&seg_header, 0, seg_header_size);
        KFileLSeek(fd, seg_header_off, SEEK_SET);
        if (KFileRead(fd, &seg_header, seg_header_size) < 0)
        {
            KPrint("[process] read program segment failed!\n");
            return -1;
        }

        if (seg_header.p_type == SEGTYPE_LOAD)
        {
            if (ProcessLoadSegment(fd, seg_header.p_offset, seg_header.p_filesize, seg_header.p_memsize, seg_header.p_vaddr) < 0)
            {
                KPrint("[process] load segment failed!\n");
                return -1;
            }

            // clear high area mem set 0
            if (seg_header.p_memsize > seg_header.p_filesize)
            {
                memset((void *)(seg_header.p_vaddr + seg_header.p_filesize), 0, seg_header.p_memsize - seg_header.p_filesize);
            }

            // figure segment end position
            seg_end = seg_header.p_vaddr + seg_header.p_memsize;

            switch (seg_header.p_flags)
            {
            case ELF32_SEGMENT_CODE:
            {
                vmm->code_start = seg_header.p_vaddr;
                vmm->code_end = PageAlign(seg_header.p_vaddr + seg_header.p_memsize);
                vmm->heap_start = PageAlign(vmm->code_end + PAGE_SIZE);
                vmm->heap_end = vmm->heap_start;
            }
            break;
            case ELF32_SEGMENT_DATA:
            {
                vmm->data_start = seg_header.p_vaddr;
                vmm->data_end = PageAlign(seg_header.p_vaddr + seg_header.p_memsize);
                vmm->heap_start = PageAlign(vmm->data_end + PAGE_SIZE);
                vmm->heap_end = vmm->heap_start;
            }
            break;
            case ELF32_SEGMENT_CODE_DATA:
            {
                vmm->code_start = seg_header.p_vaddr;
                vmm->code_end = PageAlign(seg_header.p_vaddr + seg_header.p_memsize);
                vmm->data_start = seg_header.p_vaddr;
                vmm->data_end = PageAlign(seg_header.p_vaddr + seg_header.p_memsize);
                vmm->heap_start = PageAlign(vmm->code_end + PAGE_SIZE);
                vmm->heap_end = vmm->heap_start;
            }
            break;
            }
            if (!vmm->heap_start && !vmm->heap_end)
            {
                vmm->heap_start = PageAlign(seg_end + PAGE_SIZE);
                vmm->heap_end = vmm->heap_start;
            }
        }
        // next segment header
        seg_header_off += seg_header_size;
    }
    return 0;
}

// the function is the entry of process
void ProcessEntry(void *arg)
{
    char *path = arg;
    task_t *cur = cur_task;

    SysExec(path, (const char **)cur->vmm->argu, (const char **)cur->vmm->envp);
    Panic("[process] process exception exit!!\n");
    TaskExit(-1);
}

task_t *ProcessCreate(char **argv, char **envp, uint32_t flags)
{
    task_t *task = NULL;
    task_t *parent = cur_task; // parent process is current task

    // argv empty
    if (!argv || !argv[0])
        return NULL;
    task = KMemAlloc(TASK_KERNEL_STACK_SIZE);
    if (!task)
        return NULL;
    // init task
    TaskInit(task, argv[0], TASK_PRIOR_LEVEL_NORMAL);
    // create init process
    if (flags & PROCESS_CREATE_INIT)
    {
        task->pid = USER_INIT_PROCESS_ID;
        task->threadgroup_id = task->pid;
        task->processgroup_id = 0;
        task->parent_pid = -1;
    }
    else
    {
        // general process
        if (parent)
            task->parent_pid = parent->pid;
    }
    // init fd
    if (FsFdInit(task) < 0)
    {
        KMemFree(task);
        return NULL;
    }
    // copy parent process fd
    FsFdCopyOnly(task, parent);

    if (ProcessVmmInit(task))
    {
        FsFdExit(task);
        return NULL;
    }

    KPrint("[process] ready to build arg buff!\n");
    if (VmmBuildArgBuff(task->vmm, argv, envp) < 0)
    {
        KPrint(PRINT_ERR "%s: path %s build arg buff failed!\n", __func__, argv[0]);
        ProcessVmmExit(task);
        FsFdExit(task);
        KMemFree(task);
        return NULL;
    }

    TaskStackBuild(task, ProcessEntry, task->vmm->argbuff);
    memcpy(task->vmm->argbuff, argv[0], min(strlen(argv[0]), MAX_PATH_LEN));

    TaskAddToGlobalList(task);

    if (flags & PROCESS_CREATE_STOP)
    {
        task->status = TASK_STOPPED;
    }
    else
    {
        SchedQueueAddTaskTail(SchedGetCurUnit(), task);
    }
    KPrint("[process] process %s create ok!\n", argv[0]);

    return task;
}

void ProcessMapSpaceInit(task_t *task)
{
    task->vmm->map_start = MEM_SPACE_MAP_BASE_START;
    task->vmm->map_end = MEM_SPACE_MAP_BASE_END;
}

int ProcessVmmExitWhenForking(task_t *child, task_t *parent)
{
    if (!child->vmm)
        return -1;
    VmmExitWhenForkFailed(child->vmm, parent->vmm);
}

int ProcessVmmInit(task_t *task)
{
    task->vmm = KMemAlloc(sizeof(vmm_t));
    if (!task->vmm)
        return -1;
    VmmInit(task->vmm);
    return 0;
}

int ProcessVmmExit(task_t *task)
{
    vmm_t *vmm = task->vmm;

    if (!vmm)
        return -1;
    VmmExit(vmm);
    return 0;
}

int ProcessThreadInit(task_t *task)
{
    task->pthread = KMemAlloc(sizeof(pthread_desc_t));
    if (!task->pthread)
        return -1;
    PthreadDescriptInit(task->pthread);
    return 0;
}

int ProcessThreadExit(task_t *task)
{
    if (!task->pthread)
        return -1;
    PthreadDescriptExit(task->pthread);
    task->pthread = NULL;
    return 0;
}

int ProcessRelease(task_t *task)
{
    ProcessVmmExit(task);
    ProcessThreadExit(task);
    ExceptionManagerExit(&task->exception_manager);
    TaskCancelTimer(task);
    SysPortComUnBind(-1);
}

void ProcessExecInit(task_t *task)
{
    ProcessMapSpaceInit(task);
    PthreadDescriptInit(task->pthread);
    ExceptionManagerInit(&task->exception_manager);
    TaskCancelTimer(task);
    SysPortComUnBind(-1);
    FpuInit(&task->fpu, 1); // reinit fpu
}

void ProcessTrapFrameInit(task_t *task)
{
    trap_frame_t *frame = (trap_frame_t *)TASK_GET_TRAP_FRAME(task);
    UserFrameInit(frame);
}

void ProcessCloseOtherThread(task_t *thread)
{
    task_t *task = NULL;

    list_traversal_all_owner_to_next(task, &task_global_list, global_list)
    {
        if (TASK_IN_SAME_THREAD_GROUP(task, thread))
        {
            if (task->pid != thread->pid)
            {
                ProcessCloseOneThread(thread);
            }
        }
    }

    if (thread->pthread)
    {
        AtomicSet(&thread->pthread->thread_count, 0);
    }
}

void ProcessCloseOneThread(task_t *thread)
{
    if (thread->status == TASK_READY)
        list_del_init(&thread->list);
    if (thread->status != TASK_HANGING && thread->status != TASK_ZOMBIE)
        TaskCancelTimer(thread);
    ProcessDestroy(thread, 1);
}

int ProcessDestroy(task_t *task, int thread)
{
    if (!thread)
    {
        if (!task->vmm)
            return -1;
        VmmFree(task->vmm);
        task->vmm = NULL;
    }
    TaskFree(task);
    return 0;
}

int ProcessDealZombieChild(task_t *parent)
{
    task_t *child, *next;
    int zombies = 0; // zombies counts
    int zombie = -1; // zombie pid

    list_traversal_all_owner_to_next_safe(child, next, &task_global_list, global_list)
    {
        // if task is parent task child
        if (child->parent_pid == parent->pid)
        {
            if (child->status == TASK_ZOMBIE)
            {
                if (zombie == -1)
                {
                    zombie = child->pid;
                }
                if (TASK_IS_SINGAL_THREAD(child))
                {
                    ProcessDestroy(child, 0);
                }
                else
                {
                    ProcessDestroy(child, 1);
                }
                zombies++;
            }
        }
    }
    return zombie;
}

int ProcessBuildArg(uint64_t arg_top, uint64_t *arg_bottom, const char *argv[], const char **dest_argv[])
{
    int argc = 0;
    uint64_t arg_pos = arg_top;
    uint64_t top = 0;
    char **_argv;
    char *p;
    int i;

    if (argv != NULL)
    {
        // count argv
        while (argv[argc])
        {
            argc++;
        }

        if (argc != 0)
        {
            for (i = 0; i < argc; i++)
            {
                arg_pos -= (strlen(argv[i]) + 1); // alloc argv buff
            }

            arg_pos -= (argc + 1) * sizeof(char *); // argv point buff
            arg_pos -= sizeof(uint64_t);            // argc

            if (arg_bottom)
            {
                *arg_bottom = arg_pos;
            }
            top = arg_pos;
            *(uint64_t *)PTYPE(top) = argc;
            top += sizeof(uint64_t);
            _argv = (char **)PTYPE(top);
            *dest_argv = PTYPE(top);
            p = (char *)PTYPE(top + sizeof(char *) * (argc + 1));
            for (i = 0; i < argc; i++)
            {
                _argv[i] = p;               // set argv point
                strcpy(p, argv[i]);         // copy argv to buffer
                p += (strlen(argv[i]) + 1); // next argv
            }
            _argv[i] = NULL; // set last argv point to NULL
            return argc;
        }
        else
        {
            arg_pos -= 1 * sizeof(char *);
            arg_pos -= sizeof(uint64_t);
            if (arg_bottom)
                *arg_bottom = arg_pos;
            top = arg_pos;
            *(uint64_t *)PTYPE(top) = 0;
            top += sizeof(uint64_t);
            *dest_argv = PTYPE(top);
            _argv = PTYPE(top);
            _argv[0] = NULL;
        }
    }
    *arg_bottom = arg_pos; // no build
}

int SysCreateProcess(char **argv, char **envp, uint32_t flags)
{
    task_t *task;

    if (!argv)
        return -EINVAL;
    if (!argv[0])
        return -EINVAL;
    if (SafetyCheckRange(argv[0], MAX_PATH_LEN) < 0)
        return -EFAULT;

    task = ProcessCreate(argv, envp, flags);
    if (!task)
        return -EPERM;
    return task->pid;
}

int SysResumeProcess(pid_t pid)
{
    task_t *child = TaskFindByPid(pid);
    if (!child)
        return -EPERM;
    if (child->parent_pid != cur_task->pid)
    {
        return -EPERM;
    }
    if (child->status != TASK_STOPPED)
        return -EBUSY;
    TaskWakeUp(child);
    return 0;
}
